经典面试题!从输入URL到页面展示你还不赶紧学起来?
前言🍊
“在浏览器里,从输入 URL 到页面展示,这中间发生了什么?”
这是一道经典的面试题,尤其小编之前在一次面试的时候就碰到了,当时面试官让小编尽可能多说些,小编一下蒙了,心想“这可咋讲不就是按个回车键然后请求数据渲染页面吗,有啥可讲的呀”,差点没闹出笑话,仔细思考这个问题,发现确实很深,这个过程涉及到的东西很多。这个问题的回答真的能够很好的考验一个web工程师的水平,为了同学们今后再碰到这个问题的时候可以推倒面试官,今天小编就从网络原理和浏览器原理两个方面来跟大家好好掰扯掰扯下
整体流程图
这边小编整理了一张大致的流程图,可以帮助大家更好的理解
浏览器进程
主要负责用户交互、子进程管理和文件储存等功能:包括地址栏,书签栏,前进后退按钮等部分的工作;负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问;网络进程
是面向渲染进程和浏览器进程等提供网络下载功能。渲染进程
的主要职责是负责一个 tab 内关于网页呈现的所有事情:把从网络下载的 HTML、JavaScript、CSS、图片等资源解析为可以显示和交互的页面。因为渲染进程所有的内容都是通过网络获取的,会存在一些恶意代码利用浏览器漏洞对系统进行攻击,所以运行在渲染进程里面的代码是不被信任的。这也是为什么 Chrome 会让渲染进程运行在安全沙箱里,就是为了保证系统的安全。Plugin Process
负责控制一个网页用到的所有插件,如 flashGPU Process
负责处理 GPU 相关的任务
本文我们把
ui线程
和存储器线程
放到浏览器进程来讲,google浏览器会根据不同机器的性能来控制是否将浏览器进程中的线程拆分成不同的线程,而且不同浏览器采用了不同的架构模式
浏览器进程和网络进程🍊
第一部分主要说下在输入url的过程中网络进程和浏览器进程中的ui线程做了哪些事
先说一下这部分不同线程的功能:
UI线程 :控制浏览器上的按钮及输入框; 网络进程: 处理网络请求,从网上获取数据; 存储器线程: 控制文件等的访问;
回到我们最开始的流程图,当我们在浏览器地址栏中输入文字,并点击回车获得页面内容的过程在浏览器看来可以分为以下几步:
处理输入:UI线程需要判断用户输入的是 URL
还是query
;如果是搜索内容(query)则会拼接url,否则直接访问url按下回车键 ui线程
所在的浏览器进程
首先判断是否存在像301这种返回已经记录过的,对于这种请求,首先要进行一个重定向操作,这是纯客户端的行为然后再执行下一步,否则直接执行下一步通过 进程间IPC通信
来通知网络进程
发起请求获取网页内容,ui线程控制tab加载动画开始跳转;请求的时候网络进程首先会先去查找 浏览器缓存
,因为可能请求的资源已经在浏览器上缓存过了,此时服务器上的资源如果没有增删改,就会直接用缓存过的资源,而不需要重新向服务器请求并下载资源,这样服务器减少了请求,用户也节省了带宽所以我们时常会看到状态码304,另外还会请求代理服务器缓存(nginx,apache)
网络---关于HTTP 304状态码的理解
如果没有缓存,网络进程会继续执行 DNS 查询
:域名要转换成IP才能访问到服务器,所以要去查域名对应IP。
找到对于IP后需要进行三次握手,创建TCP链接,如果是https连接,要创建加密连接,和tcp三次握手不一样,中间要有一个保证安全的过程(建立 SSL/TLS 连接
)
http请求是如何先建立的三次握手?
至于https的连接可以查看我的另一篇文章:浅谈SSL协议的握手过程🍊
发送http请求Request的数据包 接收然后数据操作后响应Response,如果是短连接直接通过四次挥手关闭连接,否则则继续使用这个管道传输数据 如果返回头中 301重定向
,网络进程会通知ui线程 服务器要求重定向,之后,另外一个 URL 请求会被触发否则网络进程会依据 Content-Type
来判断响应内容的格式,如果是文本或html用渲染进程渲染,否则如果是文件通知下载管理器;判断是html之后 Safe Browsing
会查看网页是否安全,不安全网络进程会展示警告页,此外CORB 检测(跨域资源共享)
也会触发确保敏感数据不会被传递给渲染进程;全部检查完成,网络进程确定可以导航请求网页则通知ui线程数据准备好了,ui线程会查找到一个 render(渲染器)进程
对页面进行渲染;
支线1:如果你监听过
beforeunload 事件
,这个事件再次涉及到浏览器进程
和渲染器进程
的交互,当当前页面关闭时(关闭 Tab ,刷新等等),浏览器进程 需要通知 渲染器进程 进行相关的检查,对相关事件进行处理;
支线2:除了上述流程,有些页面还拥有
Service Worker
(服务工作线程),Service Worker 让开发者对本地缓存及判断何时从网络上获取信息有了更多的控制权,如果 Service Worker 被设置为从本地 cache
中加载数据,那么就没有必要从网上获取更多数据了。
渲染器进程🍊
进入主线,这一部分我们再来讲讲过经过了上述过程,数据以及渲染进程都可用了之后,渲染器进程是怎么把页面渲染到对应的
tab
上面的:此时,地址栏会更新,展示出新页面的网页信息。history tab
会更新,可通过返回键返回导航来的页面,为了让关闭 tab 或者窗口后便于恢复,这些信息会存放在硬盘中;
主线程 Main thread 工作线程 Worker thread 排版线程 Compositor thread 光栅线程 Raster thread
其主要工作流程如下所示,哈哈哈...
好了,进入正题,渲染器工作流程如下:
主线程将 html 文件转化为浏览器能够读懂的 DOM 树结
构。其中会通过网络进程加载次级资源
,遇到 js 会停止构建 DOM 树,并执行 js。主线程将 css 文件转化为浏览器能够读懂的 styleSheets 结构
,并将其中的属性标准化,最后计算每个节点的样式。主线程通过得到的 DOM 树
和styleSheets 样式表
合成一颗布局树
并计算每个节点的具体位置。即使知道了不同元素的位置及样式信息,我们还需要知道不同元素的绘制先后顺序才能正确绘制出整个页面。在绘制阶段,主线程会遍历布局树以创建绘制记录。绘制记录可以看做是记录各元素绘制先后顺序的笔记。 复合是一种分割页面为不同的层,并单独栅格化,随后组合为帧的技术。不同层的组合由 compositor 线程(合成器线程)完成。主线程通过得到的布局树进行图层分层并得到一个图层树。 合成线程对图层进行分块处理(栅格化),并对视口区域内的图块进行位图转换(分成多个磁贴),将得到的结果通过 GPU 进程存入到 GPU 显存中。 (一旦磁贴被 光栅化
)合成线程收集位图信息(称为绘制四边形的磁贴信息)创建合成帧,并将消息通过IPC 协议
传给浏览器主进程,主进程收到消息后,会将页面内容绘制到内存中,最后再将内存显示在屏幕上。当 渲染器进程 渲染结束(渲染结束意味着该页面内的所有的页面,包括所有 iframe 都触发了 onload 时),会发送 IPC 信号到 浏览器进程, UI线程会停止展示 tab 中的 spinner。
总结:生成各种树,包括dom tree, css tree, layout tree, layer true, render tree,这些是
渲染器进程
中的GUI渲染线程
做的事。其中js脚本的解析执行是同进程下的js引擎线程来做的,也就是大名鼎鼎的V8引擎
。至于定时器回调,是定时器线程
来计数,计数完毕会把回调推入事件触发线程维护的任务队列
,当js线程空闲并且此线程维护的微任务队列无事件,才会去任务队列拿宏任务
执行处理。)
事件处理🍊
通过前面的解说,你基本已经可以游刃有余的回答文章开头的问题了,但是如果你想要加分项,可以在花几分钟读完下面的内容
如果页面有绑定事件,浏览器进程会发送事件类型及相应的坐标给渲染进程 滚动的时候产生新的ui,合成器可以独立于主线程之外直接合成新的帧而不用等到主线程的响应。但如果滚动中某个区域有绑定事件(这个区域叫做非快速滚动区域 【non-fast scrollable region】
),如果存在这个标注,合成器线程会把发生在此处的事件发送给主线程,等待主线程处理后再合成新的帧3. 但是如果整个页面都绑定了诸如 addEventListener('pointermove')
这样的事件,每次都要询问,使用passive: true
可以实现平滑滚动,但是垂直方向的滚动可能会先于event.preventDefault()
发生,此时可以通过event.cancelable
来防止这种情况。也可以使用css属性touch-action
来完全消除事件处理器的影响当组合器线程发送输入事件给主线程时,主线程依据绘制记录查找事件相关元素 出于优化的目的,Chrome 会合并 连续的事件
(如 wheel, mousewheel, mousemove, pointermove, touchmove )鼠标移动、滚动等,并延迟到下一帧渲染时候执行 。而如 keydown, keyup, mouseup, mousedown, touchstart, 和 touchend 等非连续性事件
则会立即被触发。合并事件虽然能提示性能,但是如果你的应用是绘画等,则很难绘制一条平滑的曲线了,此时可以使用 getCoalescedEvents
API 来获取组合的事件,通过 getCoalescedEvents API 获取到每一个事件(否则就变成了两个点的一条直线了最后)
作者:johnYu
链接:https://juejin.im/post/6858551640220729351
最后
看完点个赞,分享一下吧,让更多的朋友能够看到。如果你喜欢前端开发博客的分享,就给公号标个星吧,这样就不会错过我的文章了。